#include<stdio.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"

#include "driver/gpio.h"
#include "esp_system.h"

#include "button.h"

typedef struct __custom_button_t {
    uint32_t button_gpio_num;
    button_cb_t button_cb;
    button_priority_t priority;
    struct __custom_button_t   *next;
} custom_button_t;

static TaskHandle_t button_task; // 任务1结构对象
static TimerHandle_t button_timer;
static QueueHandle_t button_queue;
static bool button_inited = false;
static SemaphoreHandle_t mutex = NULL;
static const char *TAG = "button";
//button handle list head.
static custom_button_t *s_button_list_head_handle = NULL;

static bool _button_lock(void)
{
    BaseType_t ret = xSemaphoreTake(mutex, portMAX_DELAY);
    return (pdTRUE == ret) ? true : false;
}

static bool _button_unlock(void)
{
    BaseType_t ret = xSemaphoreGive(mutex);
    return (pdTRUE == ret) ? true : false;
}

static void configure_gpio(int gpio_num)
{
    gpio_config_t gpio_conf;
    gpio_conf.intr_type = GPIO_INTR_DISABLE;
    gpio_conf.mode = GPIO_MODE_INPUT;
    gpio_conf.pin_bit_mask = (1ULL << gpio_num);
    gpio_conf.pull_down_en = GPIO_PULLDOWN_ENABLE;// active level is 1.
    gpio_conf.pull_up_en = GPIO_PULLUP_DISABLE; 

    gpio_config(&gpio_conf);
    printf("%s: init gpio[%d]\r\n",TAG, gpio_num);
}

esp_err_t button_gpio_deinit(int gpio_num)
{
    /** both disable pullup and pulldown */
    gpio_config_t gpio_conf = {
        .intr_type = GPIO_INTR_DISABLE,
        .mode = GPIO_MODE_INPUT,
        .pin_bit_mask = (1ULL << gpio_num),
        .pull_down_en = GPIO_PULLDOWN_DISABLE,
        .pull_up_en = GPIO_PULLUP_DISABLE,
    };
    gpio_config(&gpio_conf);
    return ESP_OK;
}

/**
 * 获取哪一个按键按下
 */
static int get_pressed_button_gpio_num(void) {
    uint8_t state;
    int ret = ButtonNone;

    custom_button_t *target;
    for (target = s_button_list_head_handle; target; target = target->next) {
        state = gpio_get_level(target->button_gpio_num);
        if (state == 1) {
            ret = target->button_gpio_num;
            break;
        }
    }

    return ret;
}

int printf_register_button_gpio_num(void) {
    uint8_t state;

    custom_button_t *target;
    for (target = s_button_list_head_handle; target; target = target->next) {
        printf("io[%d]\r\n", target->button_gpio_num);
    }

    return 0;
}

static custom_button_t *find_button_handle(int gpio_num)
{
    custom_button_t *ret = NULL;
    custom_button_t *target;
    for (target = s_button_list_head_handle; target; target = target->next) {
        if (target->button_gpio_num == gpio_num) {
            ret = target;
            break;
        }
    }

    return ret;
}

static void button_timer_callback(TimerHandle_t timer)
{
    static int prePressedButton = ButtonNone;
    static enum {
        NO_DOWN,
        RECHECK_DOWN,
        CONFIRMED_DOWN,
        RECHECK_UP
    } scanState = NO_DOWN;

    // 读取当前硬件状态
    int currPressedButton = get_pressed_button_gpio_num();
    DEBUG_PRINT("io%d\r\n", currPressedButton);
    switch (scanState) {
        case NO_DOWN:
            // 有按键按下，进入第1次确认检查状态
            if (currPressedButton != ButtonNone) {
                scanState = RECHECK_DOWN;
                DEBUG_PRINT("NO_DOWN -> RECHECK_DOWN:%d\n", currPressedButton);
            }
            break;
        case RECHECK_DOWN:
            if (currPressedButton == ButtonNone) {
                scanState = NO_DOWN;
                DEBUG_PRINT("RECHECK_DOWN -> NO_DOWN:%d\n", currPressedButton);
            } else if (prePressedButton == currPressedButton) {
                // 发消息通知，根据按键类型，决定如何处理. 可以考虑从某个配置表中查，这样可配置多个按键事件的通知行为
                custom_button_t *current_handler = find_button_handle(currPressedButton);
                if(current_handler->priority == button_priority_high) {
                    if (xQueueSendToFront(button_queue, (void *)&currPressedButton, 0) != pdTRUE) {
                        printf("%s: failed send\n", TAG);
                    }
                } else {
                    if (xQueueSendToBack(button_queue, (void *)&currPressedButton, 0) != pdTRUE) {
                        printf("%s: failed send\n", TAG);
                    }
                }

                scanState = CONFIRMED_DOWN;
                DEBUG_PRINT("RECHECK_DOWN -> CONFIRMED_DOWN:%d\n", currPressedButton);
            }
            break;
        case CONFIRMED_DOWN:
            // 进入这个状态后，要做的是检查按键是否重复按下或者等待按键释.这里简单起见，等待按键释放
            if (currPressedButton == ButtonNone) {
                // 发现按键释放？进一步确认。但这里要return，否则prePressedButton会在后面设置None
                scanState = RECHECK_UP;
                DEBUG_PRINT("CONFIRMED_DOWN -> RECHECK_UP:%d\n", currPressedButton);
                return;
            } else if (currPressedButton != prePressedButton) {
                // 发现不一样的按键按下，重新确认
                scanState = RECHECK_DOWN;
                DEBUG_PRINT("CONFIRMED_DOWN -> RECHECK_DOWN:%d\n", currPressedButton);
            }
            break;
        case RECHECK_UP:
            if (currPressedButton == ButtonNone) {
                // 确认没有按键按下，已经释放
                scanState = NO_DOWN;
                DEBUG_PRINT("RECHECK_UP -> NO_DOWN:%d\n", currPressedButton);
            } else if (currPressedButton != prePressedButton) {
                // 发现不一样的按键按下，重新确认是否按下
                scanState = RECHECK_DOWN;
                DEBUG_PRINT("RECHECK_UP -> scanState:%d\n", currPressedButton);
            } else if (currPressedButton == prePressedButton) {
                // 相同的键，噢，前一次可能是误触发，再次重新检查
                scanState = CONFIRMED_DOWN;
                DEBUG_PRINT("RECHECK_UP -> CONFIRMED_DOWN:%d\n", currPressedButton);
            }
            break;
    }

    // 记录当前结果
    prePressedButton = currPressedButton;
}

/**
 * 等待任意按键按下
 * @param id 按键是否按下的状态
 * @return 非0，有错误发生；0无错误
 */
uint32_t ButtonWaitPress (int * gpio_num) {
    uint32_t err = 0;

    err = xQueueReceive(button_queue, (void **)gpio_num, portMAX_DELAY);
    if(!err) {
        printf("%s: queue recv err=%d\n", TAG, err);
    }
    return err;
}

void button_taskEntry (void * param) {
    custom_button_t *b_handle;
    int gpio_num;

    for (;;) {
        ButtonWaitPress(&gpio_num);
        
        b_handle = find_button_handle(gpio_num);
        b_handle->button_cb(NULL, NULL);
    }
}

/**
 * 按键初始化
 */
void ButtonInit (void) {
    mutex = xSemaphoreCreateMutex();
    if(mutex == NULL) printf("%s: mutex create fail\r\n", TAG);
    button_queue = xQueueCreate(10, sizeof(custom_button_t *));

    xTaskCreate(&button_taskEntry, "button_task", 1024*2, NULL, configMAX_PRIORITIES-10, &button_task);

    button_timer = xTimerCreate("button_timer", 20 / portTICK_PERIOD_MS, pdTRUE,
                                         NULL, button_timer_callback);

    xTimerStart(button_timer, 1);
}

/**
 * 按键反初始化
 */
void ButtonDeinit (void) {
    xTimerStop(button_timer, 10);
    xTimerDelete(button_timer, 10);
    button_timer = NULL;

    vTaskDelete(button_task);
    button_task = NULL;

    vQueueDelete(button_queue);
    button_queue = NULL;

    vSemaphoreDelete(mutex);
    mutex = NULL;

    button_inited = false;
}

button_handle_t button_create(iot_button_t button_config)
{
    custom_button_t *btn = NULL;
    btn = (custom_button_t *) calloc(1, sizeof(custom_button_t));
    if(btn == NULL) {
        printf("%s: MALLOC Fail\r\n",TAG);
        return NULL;
    }
    btn->button_gpio_num = button_config.button_gpio_num;
    btn->priority = button_config.priority;
    btn->button_cb = button_config.button_cb;
    configure_gpio(btn->button_gpio_num);
    if (!button_inited) {
        ButtonInit();
        button_inited = true;
    }

    _button_lock();
    /** Add handle to list */
    btn->next = s_button_list_head_handle;
    s_button_list_head_handle = btn;
    _button_unlock();
    return (button_handle_t)btn;
}

esp_err_t button_delete(button_handle_t btn_handle)
{
    if (btn_handle == NULL) return ESP_ERR_INVALID_ARG;
    custom_button_t *btn = (custom_button_t *)btn_handle;
    button_gpio_deinit(btn->button_gpio_num);

    custom_button_t **curr;
    _button_lock();
    for (curr = &s_button_list_head_handle; *curr; ) {
        custom_button_t *entry = *curr;
        if (entry == btn) {
            *curr = entry->next;
            free(entry);
        } else {
            curr = &entry->next;
        }
    }
    _button_unlock();

    /* count button number */
    uint16_t number = 0;
    custom_button_t *target = s_button_list_head_handle;
    while (target) {
        target = target->next;
        number++;
    }
    printf("%s: remain btn number=%d\r\n", TAG, number);

    if (0 == number && s_button_list_head_handle) { /**<  if all button is deleted, stop the timer */
        ButtonDeinit();
    }
    return ESP_OK;
}